package com.xuecheng.media.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xuecheng.model.RestResponse;
import com.xuecheng.media.mapper.MediaFilesMapper;
import com.xuecheng.media.mapper.MediaProcessMapper;
import com.xuecheng.media.model.dto.UploadFileParamsDto;
import com.xuecheng.media.service.BigFilesService;
import com.xuecheng.media.model.po.MediaFiles;
import com.xuecheng.media.model.po.MediaProcess;
import io.minio.*;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Service
@Slf4j
@RequiredArgsConstructor
public class BigFilesServiceImpl implements BigFilesService {
    private final MediaFilesMapper mediaFilesMapper;
    private final MinioClient minioClient;
    private String bucket_name = "video";

    private final MediaProcessMapper mediaProcessMapper;
    /**
     * 文件上传前检查文件
     *
     * @param fileMd5
     * @return
     */
    @Override
    public RestResponse<Boolean> checkFile(String fileMd5) {
        //检查数据库表中是否存在文件的MD5
        MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);

        //检查minio中
        if (mediaFiles != null) {
            String filePath = mediaFiles.getUrl();
            GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket(bucket_name).object(filePath).build();
            try {
                GetObjectResponse clientObject = minioClient.getObject(getObjectArgs);
                log.info("{}",clientObject);
                if (!(clientObject==null)) {
                    log.info("111");
                    return RestResponse.success(true);
                }
                clientObject.close();
            } catch (Exception e) {
                log.info("检查");
            }
        }
        log.info("不存在");
        return RestResponse.success(false);
    }

    /**
     * 检查分块是否存在
     *
     * @param fileMd5
     * @param
     * @return
     */
    @Override
    public RestResponse<Boolean> checkChunk(String fileMd5, int chunkIndex) {

        //得到分块文件目录
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        //得到分块文件的路径
        String chunkFilePath = chunkFileFolderPath + chunkIndex;

        //文件流
        InputStream fileInputStream = null;
        try {
            fileInputStream = minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(bucket_name)
                            .object(chunkFilePath)
                            .build());

            if (fileInputStream != null) {
                //分块已存在
                return RestResponse.success(true);
            }
        } catch (Exception e) {

        }
        //分块未存在
        return RestResponse.success(false);
    }
    /**
     * 上传分块
     *
     * @param
     * @param fileMd5
     * @param chunk
     * @return
     */
    @Override
    public RestResponse uplodChunk(String absolutePath, String fileMd5, int chunk) {
        log.info("上传分快");
        //获取分块文件的路径
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        try {
            UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
                    .bucket(bucket_name)
                    .object(chunkFileFolderPath.concat(Integer.toString(chunk)))
                    .filename(absolutePath)
                    .build();
            minioClient.uploadObject(uploadObjectArgs);
            //新增过程表
            addMePross(fileMd5,chunkFileFolderPath);
            return RestResponse.success(true);
        } catch (Exception e) {
            e.printStackTrace();
            log.debug("上传分块文件失败：{}", chunk);
        }
        return RestResponse.success(false);
    }

    //新增过程表
    private void addMePross(String fileMd5,String chunkFileFolderPath) {

        //标记开始上传文件
        LambdaQueryWrapper<MediaProcess> mediaProcessLambdaQueryWrapper = new LambdaQueryWrapper<>();
        mediaProcessLambdaQueryWrapper.eq(MediaProcess::getFileId,fileMd5);
        MediaProcess mediaProcess = mediaProcessMapper.selectOne(mediaProcessLambdaQueryWrapper);
        //不是第一次上传
        if(mediaProcess==null){
            mediaProcess=new MediaProcess();
            //文件标识
            mediaProcess.setFileId(fileMd5);
            //存储源
            mediaProcess.setBucket(bucket_name);
            //状态
            mediaProcess.setStatus("3");
            //上传时间
            mediaProcess.setCreateDate(LocalDateTime.now());

            int i = mediaProcessMapper.insert(mediaProcess);
            if(i<=0){
                return;
            }
        }else {
            mediaProcess.setChunkNum(mediaProcess.getChunkNum()+1);
        }
    }

    /**
     * 合并文件
     *
     * @param companyId
     * @param fileMd5
     * @param chunkTotal
     * @param uploadFileParamsDto
     * @return
     */
    @Override
    public RestResponse mergeChunks(Long companyId, String fileMd5, int chunkTotal, UploadFileParamsDto uploadFileParamsDto) {
        //合并成功后新增media
        MediaFiles mediaFiles = new MediaFiles();
        //1.获得分块文件
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        List<ComposeSource> sources = Stream.iterate(0, i -> ++i)
                .limit(chunkTotal)
                .map(i -> ComposeSource.builder()
                        .bucket(bucket_name)
                        .object(chunkFileFolderPath.concat(Integer.toString(i)))
                        .build())
                .collect(Collectors.toList());
        //文件名称
        String fileName = uploadFileParamsDto.getFilename();
        //文件扩展名
        String extName = fileName.substring(fileName.lastIndexOf("."));
        //合并文件路径
        String mergeFilePath = getFilePathByMd5(fileMd5, extName);

        //2.合并分块文件
        ComposeObjectArgs composeObjectArgs = ComposeObjectArgs
                .builder()
                .bucket(bucket_name)
                .object(mergeFilePath)
                .sources(sources)
                .build();
        try {
            minioClient.composeObject(composeObjectArgs);
        } catch (Exception e) {
            log.debug("合并文件失败：{}", fileName);
            return RestResponse.validfail(false, "合并文件失败");
        }
        //3.检验MD5
        File file = null;
        //下载数据
        GetObjectArgs object = GetObjectArgs.builder().bucket(bucket_name).object(mergeFilePath).build();
        try {
            GetObjectResponse getObjectResponse = minioClient.getObject(object);
            //创建临时文件
            file = File.createTempFile("minio", ".merge");
            //创建输入流
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            //拷贝到历史文件
            IOUtils.copy(getObjectResponse, fileOutputStream);
            fileOutputStream.close();
        } catch (Exception e) {
            log.debug("下载文件失败");
        }
        //比较两者的MD5值
        try (FileInputStream fileInputStream = new FileInputStream(file)) {
            String md5Hex = DigestUtils.md5Hex(fileInputStream);
            if (!md5Hex.equals(fileMd5)) {
                file.delete();
                fileInputStream.close();
                return RestResponse.validfail(false, "文件合并校验失败，最终上传失败。");
            }
            //设置文件大小
            mediaFiles.setFileSize(file.length());
        } catch (Exception e) {
            log.debug("校验文件失败,fileMd5:{},异常:{}", fileMd5, e.getMessage(), e);
            return RestResponse.validfail(false, "文件合并校验失败，最终上传失败。");
        } finally {
            file.delete();
        }
        //更新数据库
        //设置主键
        mediaFiles.setId(fileMd5);
        //设置机构id
        mediaFiles.setCompanyId(companyId);
        //设置文件名称
        mediaFiles.setFilename(fileName);
        //设置问价类型
        mediaFiles.setFileType("001002");
        //设置标签
        mediaFiles.setTags("课程视频");
        //设置存储的bucket
        mediaFiles.setBucket(bucket_name);
        //设置存储路径年-月-日-文件名
        mediaFiles.setFilePath(mergeFilePath);
        //文件标识
        mediaFiles.setFileId(fileMd5);
        //媒资文件访问地址
        mediaFiles.setUrl("/video/".concat(mergeFilePath));
        //上传时间
        mediaFiles.setCreateDate(LocalDateTime.now());
        //状态
        mediaFiles.setStatus("1");
        //审核状态
        mediaFiles.setAuditStatus("002003");

        int i = mediaFilesMapper.insert(mediaFiles);

        //清除分块文件
        removeObjects(chunkFileFolderPath, chunkTotal);

        //标记开始完成上传文件
        LambdaQueryWrapper<MediaProcess> mediaProcessLambdaQueryWrapper = new LambdaQueryWrapper<>();
        mediaProcessLambdaQueryWrapper.eq(MediaProcess::getFileId,fileMd5);
        MediaProcess mediaProcess = mediaProcessMapper.selectOne(mediaProcessLambdaQueryWrapper);
        if(mediaProcess!=null){
            //文件名称
            mediaProcess.setFilename(fileName);

            //状态标记为上传成功，但未处理
            mediaProcess.setStatus("1");
            //完成时间
            mediaProcess.setFinishDate(LocalDateTime.now());
            //媒资访问地址
            mediaProcess.setUrl((mergeFilePath));
            //文件路径
            mediaProcess.setFilePath(mergeFilePath);
            int update = mediaProcessMapper.updateById(mediaProcess);
        }

        return RestResponse.success();
    }


    //批量删除分块文件
    private void removeObjects(String chunkFileFolderPath, int chunkTotal) {

        List<DeleteObject> deleteObjects = Stream.iterate(0, i -> ++i)
                .limit(chunkTotal)
                .map(i -> new DeleteObject(chunkFileFolderPath.concat(Integer.toString(i))))
                .collect(Collectors.toList());
        RemoveObjectsArgs removeObjectsArgs = RemoveObjectsArgs.builder().bucket(bucket_name).objects(deleteObjects).build();
        Iterable<io.minio.Result<DeleteError>> results = minioClient.removeObjects(removeObjectsArgs);
        //必须遍历迭代器才能成功删除
        results.forEach(r -> {
            DeleteError deleteError = null;
            try {
                deleteError = r.get();
            } catch (Exception e) {
                e.printStackTrace();
                log.debug("清除分块文件失败");
            }
        });
    }

    //得到分块文件的目录
    private String getChunkFileFolderPath(String fileMd5) {
        return fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + "chunk" + "/";
    }

    //合并后的文件地址
    private String getFilePathByMd5(String fileMd5, String fileExt) {
        return fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + fileMd5 + fileExt;
    }

}
